package com.djl.context;

import com.djl.annotation.AutoWired;
import com.djl.beans.BeanDefinition;
import com.djl.beans.BeanDefinitionReader;
import com.djl.beans.BeanFactory;
import com.djl.util.CommonUtil;

import javax.servlet.ServletConfig;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static com.djl.util.CommonUtil.getReturnValueType;

/**
 * 此处是实现类,就不再往下延伸 classpath.../xml...的实现类的
 */
public class ApplicationContext implements BeanFactory {

    /** 一级缓存 **/
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** 二级缓存 **/
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** 三级缓存 **/
    private final Map<String, Object> singletonFactories = new HashMap<>(16);

    /** 正在创建的类加入此集合 **/
    private Set<String> singletonsCurrentlyInCreation = new HashSet<>();

    private BeanDefinitionReader beanDefinitionReader;

    public ApplicationContext(ServletConfig servletConfig) {
        //根据配置文件中的路径扫描所有BeanDefinition
        beanDefinitionReader = new BeanDefinitionReader(servletConfig);
        //根据 hBeanDefinitionReader扫描到的BeanDefinition 创建一个个Bean实例
        preInstantiateSingletons();
    }




    public Object getBean(String name) throws Throwable {
        Object sharedInstance = getSingleton(name);
        if(sharedInstance==null){
            sharedInstance = doCreateBean(name,beanDefinitionReader.beanDefinitionMap);
        }
        return sharedInstance;
    }

    /**
     * 实例化Bean
     */
    private Object doCreateBean(String name, Map<String, BeanDefinition> beanDefinitionMap) {
        Object singletonObject = null;
        try{
            Class<?> singletonClass=Class.forName(beanDefinitionMap.get(name).getBeanClassName());
            singletonObject=singletonClass.newInstance();
            populateBean(name,singletonClass,singletonObject);
        }catch (Exception e){
            e.printStackTrace();
        }
        return singletonObject;
    }

    /**
     * 属性赋值
     */
    private void populateBean(String beanName,Class<?> singletonClass,Object singletonObject) {
        try{
            Field[] fields = singletonClass.getDeclaredFields();
            for (Field field:fields){
                if(field.isAnnotationPresent(AutoWired.class)){
                    field.setAccessible(true);
                    this.singletonFactories.put(beanName,singletonObject);
                    this.singletonsCurrentlyInCreation.add(beanName);
                    Object bean = getBean(CommonUtil.getReturnValueType(field));
                    field.set(singletonObject,bean);
                }
            }
            this.singletonsCurrentlyInCreation.remove(beanName);
            this.singletonObjects.put(beanName,singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }catch (Throwable e){
            e.printStackTrace();
        }
    }

    /**
     * 处理循环依赖(如有A,B两对象,A拥有B,B拥有A)
     */
    private Object getSingleton(String beanName) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.singletonFactories.get(beanName);
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
        return singletonObject;
    }

    /**
     * 判断当前beanName是否正在创建
     */
    private boolean isSingletonCurrentlyInCreation(String beanName) {
        return singletonsCurrentlyInCreation.contains(beanName);
    }


    private void preInstantiateSingletons(){
        for(BeanDefinition beanDefinition:beanDefinitionReader.beanDefinitions){
            try {
                getBean(beanDefinition.getBeanName());
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }

    public Object createInstance(Object object){
        Object objInstance = null;
        try {
            objInstance = object.getClass().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return objInstance;
    }
}
